/** 
Copyright (c) 2010, Sheikh Abdul Wahid Ahmed
Details @ http://extremecodeworld.codeplex.com/license
**/

using System;
using System.Collections;
using System.Data;
using System.Text;
using System.IO;
using EXtremecode.Logging;
using System.Text.RegularExpressions;
using EXtremecode.Utils;

namespace CodeGenerator.Definitions
{
	/// <summary>
	/// Session contains two types of code.
	/// 1. Enclosed code 
	/// [e.g		display code <!--## enclosed code ##--> display code]
	///			code which will be compiled and used
	///			for generating final code by processing
	///			display code.	
	/// 2. Display code.
	///			which will become final code after processed by enclosed code.  
	/// </summary>
	public class Session
	{
		private StringBuilder content;
		private string name;
		private StringBuilder method=null;
		private StringBuilder code=null;
		private SessionCollection sessions;
	
		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="name"></param>
		/// <param name="content"></param>
		public Session(string name,StringBuilder content,SessionCollection sessions)
		{
			this.name=name;
			this.content=content;
			this.sessions=sessions;
		}
		internal StringBuilder GenerateCode(Session callingSession)
		{
			if(code==null)
			{
				code=GenerateCodeToCompile(content,name,sessions,callingSession);
			}
			return code;
		}


		/// <summary>
		/// Name
		/// </summary>
		public string Name
		{
			get
			{
				return name;
			}
		}


		/// <summary>
		/// Content
		/// </summary>
		public StringBuilder Content
		{
			get
			{
				return content;
			}
		}

		/// <summary>
		/// Method
		/// </summary>
		public StringBuilder Method
		{
			get
			{
				if(this.method==null)
				{
					string displayVarName=string.Format("codeGeneratorVariable_DisplayCode",name);
					this.method=new StringBuilder(
						string.Format("try {{ {0} }} catch(Exception ex) {{ throw Logger.Write(ex); }}",Code.ToString()));
					method.Insert(0,string.Format("public static string {0}(string codeGeneratorVariable_Prefix)\n{{ ",name) +
						string.Format("StringBuilder {0}=new StringBuilder();",displayVarName));
					method.Append(string.Format("\n return {0}.ToString();\n}}",displayVarName));

					//overloaded method  with zero argument.
					method.Append(Environment.NewLine);
					method.Append(string.Format("public static string {0}()\n{{ return {0}(string.Empty);}}",name));
				}
				return method;
			}
		}

		/// <summary>
		/// Code
		/// </summary>
		public StringBuilder Code
		{
			get
			{
				return GenerateCode(null);
			}
		}

		
		/// <summary>
		/// GenerateCodeToCompile
		/// </summary>
		/// <param name="content"></param>
		/// <param name="sessionName"></param>
		/// <param name="sessions"></param>
		/// <param name="callingSession"></param>
		/// <returns></returns>
		private static StringBuilder GenerateCodeToCompile(StringBuilder content,
			string sessionName,SessionCollection sessions,Session callingSession)
		{
			string displayVarName=string.Format("codeGeneratorVariable_DisplayCode",sessionName);
			StringBuilder codeString=new StringBuilder(Environment.NewLine);
			//Expression for seperating display code and compile code.
			Regex regex=new Regex(@"(?<before>(.|\t|\r|\n|\s)*?)(<!--|/\*)?<!--##(?<enclosedContent>(.|\t|\r|\n|\s)*?)(\s)*?##-->(\*/|-->)?(?<after>(.|\t|\r|\n|\s)*?)(?=((<!--|/\*)?<!--##)|\z)"
				,RegexOptions.Multiline);
			MatchCollection matchCol=null;
			if(content.ToString().IndexOf("<!--##")< 0 ||
				content.ToString().IndexOf("##-->")< 0)
			{
				//it is palain text no need to match contents with the above expression.
				//if we try to match this content it may hang for a long time.
			    matchCol=Regex.Matches(content.ToString(),@"(?<before>(.|\t|\r|\n|\s)*)(?<enclosedContent>)(?<after>)"); 
				
			}
			else
			{
				matchCol=regex.Matches(content.ToString());
			}
			
			//Expression for getting rows, seperated by newLine.
			regex=new Regex(@"(?<before>.*?)(?<newLine>(\n|\r|\v|\z)+)(?<after>.*?)(?=\n|\r|\v|\z)"
				,RegexOptions.Multiline);

			
			foreach(Match match in matchCol)
			{
				string beforeDisplayCode= match.Groups["before"].Value.TrimEnd('\r','\n'); 
				string afterDisplayCode=match.Groups["after"].Value.TrimEnd('\r','\n');
				string enclosedContent=match.Groups["enclosedContent"].Value;
				
				// Before enclosed content display code.
				if(string.Empty != beforeDisplayCode)
				{
					int lastNewLineIndex = beforeDisplayCode.LastIndexOf(Environment.NewLine);
					if(lastNewLineIndex > -1  && 
						beforeDisplayCode.Substring(lastNewLineIndex + Environment.NewLine.Length).Trim()==string.Empty)      
					{
						beforeDisplayCode=beforeDisplayCode.Substring(0,lastNewLineIndex);	
					}

					beforeDisplayCode=beforeDisplayCode.Replace("\"","\\\""); // replace '"' to '\"'
 
					string lastNewLine=string.Empty;
					foreach(Match newLineMatch in regex.Matches(beforeDisplayCode))
					{
						string beforeNewLine= newLineMatch.Groups["before"].Value; 
						string afterNewLine=newLineMatch.Groups["after"].Value;
						string newLine=newLineMatch.Groups["newLine"].Value;
						
						//we need to display escaped charactor. because its display code. 
						newLine=newLine.Replace("\n","\\n");
						newLine=newLine.Replace("\r","\\r");
						newLine=newLine.Replace("\t","\\t");

						if(beforeNewLine.Trim() != string.Empty && lastNewLine.Trim() != string.Empty)
						{
							beforeNewLine="\"\\r\\n\" + codeGeneratorVariable_Prefix + \"" + beforeNewLine + "\"";   
						}
						else
						{
							beforeNewLine= "\"" + beforeNewLine + "\"";
						}

						if(afterNewLine.Trim() != string.Empty)
						{
							afterNewLine="codeGeneratorVariable_Prefix + \"" + afterNewLine + "\"";   
						}
						else
						{
							afterNewLine= "\"\"";
						}

						codeString.Append(string.Format("{0}.Append({1} + \"{2}\" + {3});",
							displayVarName,beforeNewLine,newLine,afterNewLine)+ Environment.NewLine);
						lastNewLine=newLine;
					}
					codeString.Append(Environment.NewLine); 
				}
				
				
				// Enclosed content.
				if(string.Empty != enclosedContent)
				{
					if(enclosedContent.Trim().IndexOf("=")==0)
					{
						//if start with '=' then encloded content will be considered as display content
						//and string followed by '=' used as variable. 
						codeString.Append(string.Format("{0}.Append({1});",
							displayVarName,enclosedContent.Trim().Substring(1)) + Environment.NewLine);

					}
					else
					{
						foreach(Match newLineMatch in regex.Matches(enclosedContent))
						{
							string beforeNewLine= newLineMatch.Groups["before"].Value; 
							string afterNewLine=newLineMatch.Groups["after"].Value;
							string newLine=newLineMatch.Groups["newLine"].Value;
							
							/*Check beforeNewLine and afterNewLine,
							if they are including statements consider their values
							as session name and repalce session name with its code.
							so use getincluded method for this purpose. 
							*/
							beforeNewLine=GetIncludedCode(beforeNewLine,sessionName,
								sessions,callingSession);
							
							afterNewLine=GetIncludedCode(afterNewLine,sessionName,
								sessions,callingSession);


							codeString.Append(beforeNewLine+newLine+afterNewLine);
						}
					}
					codeString.Append(Environment.NewLine);
				}

				// After enclosed content display code.
				if(string.Empty != afterDisplayCode)
				{
					int lastNewLineIndex = afterDisplayCode.LastIndexOf(Environment.NewLine);
					if(lastNewLineIndex > -1  && 
						afterDisplayCode.Substring(lastNewLineIndex + Environment.NewLine.Length).Trim()==string.Empty)      
					{
						 afterDisplayCode=afterDisplayCode.Substring(0,lastNewLineIndex);	
					}

					afterDisplayCode=afterDisplayCode.Replace("\"","\\\""); // replace '"' to '\"' 
					
					string lastNewLine=string.Empty;
					foreach(Match newLineMatch in regex.Matches(afterDisplayCode))
					{
						string beforeNewLine= newLineMatch.Groups["before"].Value; 
						string afterNewLine=newLineMatch.Groups["after"].Value;
						string newLine=newLineMatch.Groups["newLine"].Value;
						
						//we need to display escaped charactor. because it is display code. 
						newLine=newLine.Replace("\n","\\n");
						newLine=newLine.Replace("\r","\\r");

						if(beforeNewLine.Trim() != string.Empty && lastNewLine.Trim() != string.Empty)
						{
							beforeNewLine="\"\\r\\n\" + codeGeneratorVariable_Prefix + \"" + beforeNewLine + "\"";   
						}
						else
						{
							beforeNewLine= "\"" + beforeNewLine + "\"";
						}

						if(afterNewLine.Trim() != string.Empty)
						{
							afterNewLine="codeGeneratorVariable_Prefix + \"" + afterNewLine + "\"";   
						}
						else
						{
							afterNewLine= "\"\"";
						}

						codeString.Append(string.Format("{0}.Append({1} + \"{2}\" + {3});",
							displayVarName,beforeNewLine,newLine,afterNewLine)+ Environment.NewLine);
						lastNewLine=newLine;
					}
					codeString.Append(Environment.NewLine);
				}

			}
			
			return codeString;
		}

		private static string GetIncludedCode(string includingcode,string sessionName,SessionCollection sessions,Session callingSession)
		{
			Regex regexInclude=new Regex(@"\s*<%@\s*(?<sessionName>\w+)\s*%>\s*");
			MatchCollection includeMatches;
							
			
			includeMatches=regexInclude.Matches(includingcode);
			if(includeMatches.Count>0)
			{
				includingcode=Environment.NewLine;
			
				foreach(Match includeMatch in includeMatches)
				{
					string includedSessionName=includeMatch.Groups["sessionName"].Value;
					if(sessions.ContainsKey(includedSessionName))
					{
						if(sessions[includedSessionName] != callingSession) 
						{
							includingcode += sessions[includedSessionName].Code.
								ToString().Trim('/','*',' ','\n','\r','\t')
								+ Environment.NewLine;  
						}
						else
						{
							Logger.Write(new Exception(
								string.Format("Cross reference session '{0}'",includedSessionName)
								));

						}
					}
					else
					{
						Logger.Write(new Exception(
							string.Format("Session Name:{0} not defined",includedSessionName)
							));
					}
				}
				includingcode+= Environment.NewLine;
			}
			return includingcode;

		}
		

		

	}

	

	/// <summary>
	/// Summary description for Session.
	/// </summary>
	public class SessionCollection:Hashtable
	{
		/// <summary>
		/// Constructor
		/// </summary>
		public SessionCollection()
		{
			//
			// TODO: Add constructor logic here
			//
		}

		/// <summary>
		/// LoadSessions
		/// </summary>
		/// <param name="fileName"></param>
		public void LoadSessions(string fileName)
		{
			StringBuilder sb=FileUtil.ReadFileData(fileName);
			LoadSessions(sb);
		}

		
		/// <summary>
		/// LoadSessions
		/// </summary>
		/// <param name="stream"></param>
		public void LoadSessions(Stream stream)
		{
			StringBuilder sb=FileUtil.ReadStreamData(stream);
			LoadSessions(sb);
		}

		/// <summary>
		/// LoadSessions
		/// </summary>
		/// <param name="data"></param>
		public void LoadSessions(StringBuilder data)
		{
			
			Regex regex=new Regex(@"(#region(|\t|\r|\n|\s)*)?<!--##(\s)*session(\s)+(?<sessionName>\w+)(\s)*##-->(?<content>(.|\t|\r|\n|\s)*?)<!--##/(\s)*session(\s)*##-->((|\t|\r|\n|\s)*#endregion)?"
				,RegexOptions.Multiline);
			MatchCollection matchCol=regex.Matches(data.ToString());
			
			foreach(Match match in matchCol)
			{
				string sessionName= match.Groups["sessionName"].Value; 
				string content=match.Groups["content"].Value;
				Session session=new Session(sessionName,new StringBuilder(content),this);
				Add(session);
				
			}


		}


		
		
		/// <summary>
		/// Add
		/// </summary>
		/// <param name="session"></param>
		public void Add(Session session)
		{
			
			try
			{
				base.Add(session.Name,session); 
			}
			catch(Exception ex)
			{
				throw Logger.Write(string.Format("Session Name:{0}",session.Name),ex);
			}			
		}
		
		/// <summary>
		/// Add
		/// </summary>
		/// <param name="key"></param>
		/// <param name="value"></param>
		public override void Add(object key, object value)
		{
			throw Logger.Write(new Exception("Obsolete method"));
		}
		
		
		/// <summary>
		/// Indexer
		/// </summary>
		public Session this[string key]
		{
			get
			{
				return base[key] as Session; 
			}
		}

	}
}
